/*
 * Copyright (c) 2022, yifengling0. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. No personal names or organizations' names associated with the
 *    Atomthreads project may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE ATOMTHREADS PROJECT AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "atom.h"
#include "stm8l15x_clk.h"
#include "stm8l15x_spi.h"
#include "drv_pin.h"
#include "drv_spi.h"
#include "pin.h"


static int simSpiXferUnit(Stm8Spi *dev, int val)
{
    int dataLines = dev->Super.Protected.dataLines;
    uint8_t result = 0;

    if(  dataLines == SPI_Direction_1Line_Tx || dataLines == SPI_Direction_2Lines_FullDuplex ){
        /*!< Wait until SPI is not busy */  
        while (SPI_GetFlagStatus(dev->Protected.dev, SPI_FLAG_BSY) != RESET);
        SPI_SendData(dev->Protected.dev, val);
        while (SPI_GetFlagStatus(dev->Protected.dev, SPI_FLAG_BSY) != RESET);
    }

    if(dataLines == SPI_Direction_1Line_Rx || dataLines == SPI_Direction_2Lines_FullDuplex ){
        /*!< Wait until SPI is not busy */
        while (SPI_GetFlagStatus(dev->Protected.dev, SPI_FLAG_BSY) == SET);
        while (SPI_GetFlagStatus(dev->Protected.dev, SPI_FLAG_RXNE) == RESET);
        result = SPI_ReceiveData(dev->Protected.dev);
    }

	return result;
}

// 0 : tx 1 : rx
static void switchTxRx(Stm8Spi *dev, SPI_Direction_TypeDef mode)
{
    if( dev->Super.Protected.dataLines != SPI_Direction_2Lines_FullDuplex && 
        dev->Super.Protected.dataLines != SPI_Direction_2Lines_RxOnly){
        if( mode == SPI_Direction_Tx ){
            dev->Super.Protected.dataLines = SPI_Direction_1Line_Tx;
        }else{
            dev->Super.Protected.dataLines = SPI_Direction_1Line_Rx;
        }
        SPI_BiDirectionalLineConfig(dev->Protected.dev, mode);
    }
}

static int stm8lSpiXfer(SpiBase *dev, const void *send_buf, void *recv_buf, size_t length)
{
	Stm8Spi *pDrv = (Stm8Spi *)dev;
    
    if(pDrv->Protected.ctrl->autoCs){
        PinWrite(pDrv->Protected.ctrl->cs, LOW);    
    }

	if (send_buf && recv_buf)
	{
		uint8_t *txBuf = (uint8_t *)send_buf;
		uint8_t *rxBuf = (uint8_t *)recv_buf;
		int bufLen = length;
		while (bufLen-- > 0)
		{
			*rxBuf++ = simSpiXferUnit(pDrv, *txBuf++);
		}
	}
	else if (recv_buf)
	{
		uint8_t *rxBuf = (uint8_t *)recv_buf;
		int bufLen = length;
        switchTxRx(pDrv, SPI_Direction_Rx);
        while (bufLen-- > 0)
		{
            *rxBuf++ = simSpiXferUnit(pDrv, 0xff);
		}
        switchTxRx(pDrv, SPI_Direction_Tx);
	}
	else if (send_buf)
	{
		uint8_t *txBuf = (uint8_t *)send_buf;
		int bufLen = length;
        switchTxRx(pDrv, SPI_Direction_Tx);
		while (bufLen-- > 0)
		{
			simSpiXferUnit(pDrv, *txBuf++);
		}
	}
    
    if(pDrv->Protected.ctrl->autoCs){
        PinWrite(pDrv->Protected.ctrl->cs, HIGH);
    }

	return 0;
}

static void  simSpiModeCfg(Stm8Spi* dev, const Stm8lSpiDevCtrl* ctrl)
{
    //开启SPI外设时钟
    CLK_PeripheralClockConfig(ctrl->clock, ENABLE);
    PinMode(ctrl->cs, PIN_MODE_OUTPUT);  
    // 端口复用的情况下，需要设置为浮动上拉
    PinMode(ctrl->scl, PIN_MODE_INPUT_PULLUP);    
    PinMode(ctrl->mosi, PIN_MODE_INPUT_PULLUP);
    PinMode(ctrl->miso, PIN_MODE_INPUT_PULLUP); 

    SPI_DeInit(dev->Protected.dev);

    SPI_Init(dev->Protected.dev, SPI_FirstBit_MSB, SPI_BaudRatePrescaler_256, (SPI_Mode_TypeDef)dev->Super.Protected.mode, 
             (SPI_CPOL_TypeDef)dev->Super.Protected.cpol, (SPI_CPHA_TypeDef)dev->Super.Protected.cpha, (SPI_DirectionMode_TypeDef)dev->Super.Protected.dataLines, SPI_NSS_Soft, 0x07);

    SPI_Cmd(dev->Protected.dev, ENABLE);
}

void Stm8lCreate(Stm8Spi* dev, const char *name,  SPI_TypeDef* spi, int mode, int dataLines, int cpol, int cpha, int bitwidth , uint32_t freq, const Stm8lSpiDevCtrl* ctrl)
{
    dev->Protected.dev = spi;
    dev->Protected.ctrl = ctrl;
    SpiCreate(&dev->Super, name, mode, dataLines, cpol, cpha, bitwidth, freq, stm8lSpiXfer);
    simSpiModeCfg(dev, ctrl);
}